﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Xceed.Silverlight.DataGrid.Utils;

namespace Xceed.Silverlight.DataGrid
{
  internal class ContentRepository : FrameworkElement, INotifyPropertyChanged
  {
    #region Constructor

    internal ContentRepository()
    {
      m_propertyChangedHelper = new NotifyPropertyChangedHelper( this, this.RaisePropertyChangedEvent );
      m_validationErrors = new ValidationErrorsObservableCollection<ValidationError>();
      m_readOnlyValidationErrors = new ReadOnlyObservableCollection<ValidationError>( m_validationErrors );

      m_validationErrors.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnValidationErrorsCollectionChanged );

#if SILVERLIGHT
      this.BindingValidationError += new EventHandler<ValidationErrorEventArgs>( this.OnBindingValidationError );
#endif
    }

    #endregion

    #region ValidationErrors Property

    public ReadOnlyObservableCollection<ValidationError> ValidationErrors
    {
      get
      {
        return m_readOnlyValidationErrors;
      }
    }

    private readonly ReadOnlyObservableCollection<ValidationError> m_readOnlyValidationErrors;
    private readonly ValidationErrorsObservableCollection<ValidationError> m_validationErrors;

    #endregion

    #region HasValidationErrors Property

    public bool HasValidationErrors
    {
      get
      {
        return ( this.ValidationErrors.Count > 0 );
      }
    }

    #endregion

    #region IsDirty Property

    public bool IsDirty
    {
      get
      {
        return m_isDirty;
      }
      private set
      {
        if( value != m_isDirty )
        {
          m_isDirty = value;

          this.RaisePropertyChanged( () => this.IsDirty );
        }
      }
    }

    private bool m_isDirty;

    #endregion

    #region Content Property

    private static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
      "Content",
      typeof( object ),
      typeof( ContentRepository ),
      new PropertyMetadata( null, new PropertyChangedCallback( ContentRepository.OnContentChanged ) ) );

    public object Content
    {
      get
      {
        return this.GetValue( ContentRepository.ContentProperty );
      }
      private set
      {
        this.SetValue( ContentRepository.ContentProperty, value );
      }
    }

    private static void OnContentChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      var self = ( ContentRepository )sender;

      self.OnContentChanged( e.NewValue, e.OldValue );
    }

    private void OnContentChanged( object newValue, object oldValue )
    {
      if( this.IsCleared )
        return;

      this.RaisePropertyChanged( () => this.Content );
    }

    #endregion

    #region IsCleared Private Property

    private bool IsCleared
    {
      get
      {
        return m_isCleared;
      }
    }

    private bool m_isCleared;

    #endregion

    internal void SetContentBinding( Binding binding )
    {
      if( this.IsCleared )
        return;

      this.ApplyContentBinding( this.CoerceBinding( binding ) );
    }

    internal void SetContent( object value )
    {
      if( this.IsCleared || ( this.Content == value ) )
        return;

      this.IsDirty = true;
      this.Content = value;
    }

    internal void UpdateSource()
    {
      if( this.IsCleared || !this.IsDirty )
        return;

      var be = this.GetBindingExpression( ContentRepository.ContentProperty );
      if( ( be == null ) || ( be.ParentBinding.Mode != BindingMode.TwoWay ) )
        return;

      be.UpdateSource();

      this.IsDirty = false;
      this.UpdateValidationErrors();
    }

    internal void RefreshContent()
    {
      if( this.IsCleared )
        return;

      var be = this.GetBindingExpression( ContentRepository.ContentProperty );
      Binding binding = ( be != null ) ? be.ParentBinding : null;

      this.ApplyContentBinding( binding );
    }

    internal void RefreshValidationErrors()
    {
      if( this.IsCleared )
        return;

      this.UpdateValidationErrors();
    }

    internal void Clear()
    {
      if( this.IsCleared )
        return;

      m_isCleared = true;
      m_isDirty = false;
      m_propertyChangedHandler = null;
      m_validationErrors.CollectionChanged -= new NotifyCollectionChangedEventHandler( this.OnValidationErrorsCollectionChanged );
#if SILVERLIGHT
      this.BindingValidationError -= new EventHandler<ValidationErrorEventArgs>( this.OnBindingValidationError );
#endif

      this.ClearValue( ContentRepository.ContentProperty );
    }

    private void ApplyContentBinding( Binding binding )
    {
      var dp = ContentRepository.ContentProperty;

      if( binding != null )
      {
        this.SetBinding( dp, binding );
      }
      else
      {
        this.ClearValue( dp );
      }

      this.IsDirty = false;
      this.UpdateValidationErrors();
    }

    private void UpdateValidationErrors()
    {
      var errors = Validation.GetErrors( this );
      var hasError = ( errors != null ) && ( errors.Count > 0 );

      if( hasError || ( m_validationErrors.Count > 0 ) )
      {
        // The batch operation will raise the CollectionChanged event
        // only once after the last operation.
        using( m_validationErrors.Batch() )
        {
          m_validationErrors.Clear();

          foreach( var error in errors )
          {
            m_validationErrors.Add( error );
          }
        }
      }
    }

    private Binding CoerceBinding( Binding binding )
    {
      if( binding == null )
        return null;

      // Create an exact copy of the binding with validation errors notification
      // and with explicit update source.
      var newBinding = new Binding();
      newBinding.BindsDirectlyToSource = binding.BindsDirectlyToSource;
      newBinding.Converter = binding.Converter;
      newBinding.ConverterCulture = binding.ConverterCulture;
      newBinding.ConverterParameter = binding.ConverterParameter;
      newBinding.FallbackValue = binding.FallbackValue;
      newBinding.Mode = binding.Mode;
      newBinding.NotifyOnValidationError = true;
      newBinding.Path = binding.Path;
      newBinding.StringFormat = binding.StringFormat;
      newBinding.TargetNullValue = binding.TargetNullValue;
      newBinding.UpdateSourceTrigger = System.Windows.Data.UpdateSourceTrigger.Explicit;
      newBinding.ValidatesOnDataErrors = true;
      newBinding.ValidatesOnExceptions = true;
#if SILVERLIGHT
      newBinding.ValidatesOnNotifyDataErrors = true;
#endif

      if( binding.ElementName != null )
      {
        newBinding.ElementName = binding.ElementName;
      }
      else if( binding.RelativeSource != null )
      {
        newBinding.RelativeSource = binding.RelativeSource;
      }
      else
      {
        newBinding.Source = binding.Source;
      }

      return newBinding;
    }

    private void OnValidationErrorsCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      if( this.IsCleared )
        return;

      this.RaisePropertyChanged( () => this.HasValidationErrors );
    }

    private void OnBindingValidationError( object sender, ValidationErrorEventArgs e )
    {
      if( this.IsCleared )
        return;

      this.UpdateValidationErrors();
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged
    {
      add
      {
        if( this.IsCleared )
          return;

        m_propertyChangedHandler += value;
      }
      remove
      {
        if( this.IsCleared )
          return;

        m_propertyChangedHandler -= value;
      }
    }

    protected void RaisePropertyChanged<TMember>( Expression<Func<TMember>> expression )
    {
      m_propertyChangedHelper.RaisePropertyChanged( expression );
    }

    private void RaisePropertyChangedEvent( string propertyName )
    {
      if( m_propertyChangedHandler != null )
      {
        m_propertyChangedHandler.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
      }
    }

    private readonly NotifyPropertyChangedHelper m_propertyChangedHelper;
    private PropertyChangedEventHandler m_propertyChangedHandler;

    #endregion
  }
}
